// I2C主控制器
// 版本：V2.0
// 作者：杨纯一
// 日期：2025-9-30


module i2c_master (
    // 系统接口
    input wire clk,           // 系统时钟(20-50MHz)
    input wire reset,         // 异步复位信号(高有效)
    
    // 控制接口
    input wire i_req,         // 传输请求信号
    input wire i_rw,          // 读写控制: 0=写, 1=读
    input wire i_fast_mode,   // 速率模式: 0=标准模式(100kHz), 1=快速模式(400kHz)
    input wire [6:0] i_dev_addr, // 7位从设备地址
    input wire [7:0] i_data,  // 发送数据
    output reg [7:0] o_data,  // 接收数据
    output reg o_busy,        // 忙信号指示
    output reg o_ack_error,   // 应答错误指示
    
    // I2C总线接口
    inout wire sda,           // I2C数据线
    inout wire scl            // I2C时钟线
);
    
    // 参数定义
    parameter SYS_CLOCK = 20_000_000;  // 系统时钟频率
    
    // 状态机状态定义
    localparam IDLE       = 4'b0000;
    localparam START      = 4'b0001;
    localparam SEND_ADDR  = 4'b0010;
    localparam SEND_DATA  = 4'b0011;
    localparam RECV_DATA  = 4'b0100;
    localparam ACK_CHECK  = 4'b0101;
    localparam ACK_GEN    = 4'b0110;
    localparam NACK_GEN   = 4'b0111;
    localparam STOP       = 4'b1000;
    localparam WAIT_SCL_HIGH = 4'b1001;  // 等待SCL变高（时钟同步）
    localparam ARB_LOST   = 4'b1010;
    
    // 寄存器定义
    reg [3:0] state, next_state;  // 状态寄存器
    reg [15:0] clk_cnt;           // 时钟计数器
    reg [15:0] scl_cnt_max;       // SCL分频系数最大值
    reg [3:0] bit_cnt;            // 位计数器
    reg [1:0] scl_phase;          // SCL相位计数器 (0-3)
    reg [7:0] addr_rw_reg;        // 地址+读写寄存器
    reg [7:0] tx_data_reg;        // 发送数据寄存器
    reg [7:0] rx_data_reg;        // 接收数据寄存器
    reg sda_oe;                   // SDA输出使能
    reg sda_out;                  // SDA输出数据
    reg scl_oe;                   // SCL输出使能
    reg scl_out;                  // SCL输出数据
    reg ack_error;                // 应答错误标志
    reg arbitration_lost;         // 仲裁丢失标志
    reg sda_in_prev;              // SDA输入
    reg scl_in_prev;              // SCL输入
    
    // 输入同步寄存器
    reg sda_in_sync;
    reg scl_in_sync;
    
    // 同步输入信号
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            sda_in_sync <= 1'b1;
            scl_in_sync <= 1'b1;
            sda_in_prev <= 1'b1;
            scl_in_prev <= 1'b1;
        end else begin
            sda_in_sync <= sda;
            scl_in_sync <= scl;
            sda_in_prev <= sda_in_sync;  // 保存上一个周期的SDA值
            scl_in_prev <= scl_in_sync;  // 保存上一个周期的SCL值
        end
    end
    
    // 检测SCL上升沿
    wire scl_rising_edge = (scl_in_sync && !scl_in_prev);
    
    // 检测SCL下降沿
    wire scl_falling_edge = (!scl_in_sync && scl_in_prev);
    
    // 根据模式选择分频系数
    always @(*) begin
        if (i_fast_mode)
            scl_cnt_max = (SYS_CLOCK / (400_000 * 4)) - 1;  // 快速模式: 400kHz
        else
            scl_cnt_max = (SYS_CLOCK / (100_000 * 4)) - 1;  // 标准模式: 100kHz
    end
    
    // SCL时钟生成器
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            clk_cnt <= 0;
            scl_out <= 1'b1;
            scl_phase <= 2'b00;
        end else if (state != IDLE && state != ARB_LOST && state != WAIT_SCL_HIGH) begin
            if (clk_cnt >= scl_cnt_max) begin
                clk_cnt <= 0;
                scl_phase <= scl_phase + 1;
                
                // 每4个相位周期翻转一次SCL，生成50%占空比
                if (scl_phase == 2'b11) begin
                    scl_out <= ~scl_out;
                end
            end else begin
                clk_cnt <= clk_cnt + 1;
            end
        end else begin
            clk_cnt <= 0;
            scl_phase <= 2'b00;
            scl_out <= 1'b1;
        end
    end
    
    // 仲裁检测逻辑
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            arbitration_lost <= 1'b0;
        end else if (scl_oe && scl_out && state != IDLE && state != ARB_LOST) begin
            // 在SCL为高且主设备正在驱动总线时检测仲裁
            if (sda_oe && sda_out == 1'b1 && sda_in_sync == 1'b0) begin
                // 主设备试图输出高电平，但总线被拉低，仲裁丢失
                arbitration_lost <= 1'b1;
            end
        end
    end
    
    // 状态机转移逻辑
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            state <= IDLE;
        end else if (arbitration_lost) begin
            state <= ARB_LOST;
        end else begin
            state <= next_state;
        end
    end
    
    // 下一个状态决策
    always @(*) begin
        case (state)
            IDLE:
                next_state = (i_req && !o_busy) ? START : IDLE;
            
            START:
                next_state = (scl_phase == 2'b11) ? SEND_ADDR : START;
            
            SEND_ADDR:
                if (bit_cnt == 0)
                    next_state = WAIT_SCL_HIGH;
                else
                    next_state = SEND_ADDR;
            
            SEND_DATA:
                if (bit_cnt == 0)
                    next_state = WAIT_SCL_HIGH;
                else
                    next_state = SEND_DATA;
            
            RECV_DATA:
                if (bit_cnt == 0)
                    next_state = (i_rw) ? NACK_GEN : ACK_GEN;
                else
                    next_state = RECV_DATA;
            
            WAIT_SCL_HIGH:
                // 等待SCL变高（时钟同步）
                next_state = (scl_in_sync) ? ACK_CHECK : WAIT_SCL_HIGH;
            
            ACK_CHECK:
                if (scl_phase == 2'b11)
                    next_state = (ack_error) ? STOP : 
                                (i_rw) ? RECV_DATA : SEND_DATA;
                else
                    next_state = ACK_CHECK;
            
            ACK_GEN:
                next_state = (scl_phase == 2'b11) ? WAIT_SCL_HIGH : ACK_GEN;
            
            NACK_GEN:
                next_state = (scl_phase == 2'b11) ? WAIT_SCL_HIGH : NACK_GEN;
            
            STOP:
                next_state = (scl_phase == 2'b11) ? IDLE : STOP;
            
            ARB_LOST:
                next_state = (i_req) ? ARB_LOST : IDLE;
            
            default:
                next_state = IDLE;
        endcase
    end
    
    // 主控制逻辑
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            sda_oe <= 0;
            sda_out <= 1;
            scl_oe <= 0;
            bit_cnt <= 8;
            addr_rw_reg <= 8'b0;
            tx_data_reg <= 8'b0;
            rx_data_reg <= 8'b0;
            o_data <= 8'b0;
            o_busy <= 0;
            o_ack_error <= 0;
            ack_error <= 0;
            arbitration_lost <= 0;
        end else begin
            case (state)
                IDLE: begin
                    sda_oe <= 0;
                    sda_out <= 1;
                    scl_oe <= 0;
                    bit_cnt <= 8;
                    o_busy <= 0;
                    o_ack_error <= 0;
                    ack_error <= 0;
                    arbitration_lost <= 0;
                    
                    if (i_req && !o_busy) begin
                        addr_rw_reg <= {i_dev_addr, i_rw}; // 7位地址+1位读写位
                        tx_data_reg <= i_data;
                        o_busy <= 1;
                    end
                end
                
                START: begin
                    scl_oe <= 1; // 使能SCL输出
                    
                    case (scl_phase)
                        2'b00: begin
                            sda_oe <= 1;
                            sda_out <= 1; // SDA置高
                        end
                        2'b01: begin
                            sda_out <= 0; // SCL高时拉低SDA，生成起始条件
                        end
                        2'b10: begin
                            scl_out <= 0; // SCL拉低，准备数据传输
                        end
                        2'b11: begin
                            // 相位结束，状态机会转移到SEND_ADDR
                        end
                    endcase
                end
                
                SEND_ADDR: begin
                    scl_oe <= 1;
                    
                    if (bit_cnt > 0) begin
                        case (scl_phase)
                            2'b00: begin
                                sda_oe <= 1;
                                sda_out <= addr_rw_reg[bit_cnt-1]; // 输出当前地址/数据位
                            end
                            2'b01: begin
                                // SCL变高，从设备采样数据
                                // 检查时钟同步
                                if (!scl_in_sync) begin
                                    // 从设备拉低了SCL，需要等待
                                    scl_oe <= 0;
                                end
                            end
                            2'b10: begin
                                scl_out <= 0; // SCL拉低
                            end
                            2'b11: begin
                                bit_cnt <= bit_cnt - 1; // 位计数器递减
                            end
                        endcase
                    end else begin
                        sda_oe <= 0; // 释放SDA线（准备接收从机ACK）
                    end
                end
                
                SEND_DATA: begin
                    scl_oe <= 1;
                    
                    if (bit_cnt > 0) begin
                        case (scl_phase)
                            2'b00: begin
                                sda_oe <= 1;
                                sda_out <= tx_data_reg[bit_cnt-1]; // 输出当前数据位
                            end
                            2'b01: begin
                                // SCL变高，从设备采样数据
                                // 检查时钟同步
                                if (!scl_in_sync) begin
                                    // 从设备拉低了SCL，需要等待
                                    scl_oe <= 0;
                                end
                            end
                            2'b10: begin
                                scl_out <= 0; // SCL拉低
                            end
                            2'b11: begin
                                bit_cnt <= bit_cnt - 1; // 位计数器递减
                            end
                        endcase
                    end else begin
                        sda_oe <= 0; // 释放SDA线（准备接收从机ACK）
                    end
                end
                
                RECV_DATA: begin
                    scl_oe <= 1;
                    
                    if (bit_cnt > 0) begin
                        case (scl_phase)
                            2'b00: begin
                                sda_oe <= 0; // 释放SDA，准备接收数据
                            end
                            2'b01: begin
                                // SCL高电平时采样SDA数据
                                rx_data_reg <= {rx_data_reg[6:0], sda_in_sync};
                                
                                // 检查时钟同步
                                if (!scl_in_sync) begin
                                    // 从设备拉低了SCL，需要等待
                                    scl_oe <= 0;
                                end
                            end
                            2'b10: begin
                                scl_out <= 0; // SCL拉低
                            end
                            2'b11: begin
                                bit_cnt <= bit_cnt - 1; // 位计数器递减
                            end
                        endcase
                    end else begin
                        o_data <= rx_data_reg; // 输出接收的数据
                    end
                end
                
                WAIT_SCL_HIGH: begin
                    // 等待SCL被从设备释放（时钟同步）
                    scl_oe <= 0; // 释放SCL，让从设备控制
                    
                    if (scl_in_sync) begin
                        scl_oe <= 1; // 从设备已释放SCL，重新取得控制
                        scl_out <= 0; // 准备将SCL拉低
                    end
                end
                
                ACK_CHECK: begin
                    scl_oe <= 1;
                    
                    case (scl_phase)
                        2'b00: begin
                            sda_oe <= 0; // 释放SDA，准备检测ACK
                        end
                        2'b01: begin
                            ack_error <= sda_in_sync; // 检测SDA电平：1为NACK，0为ACK
                            o_ack_error <= sda_in_sync;
                        end
                        2'b10: begin
                            scl_out <= 0; // SCL拉低
                        end
                        2'b11: begin
                            if (!ack_error) begin
                                bit_cnt <= 8; // 重置位计数器
                            end
                        end
                    endcase
                end
                
                ACK_GEN: begin
                    scl_oe <= 1;
                    
                    case (scl_phase)
                        2'b00: begin
                            sda_oe <= 1;
                            sda_out <= 0; // 生成ACK
                        end
                        2'b01: begin
                            // SCL高电平，从设备检测ACK
                        end
                        2'b10: begin
                            scl_out <= 0; // SCL拉低
                        end
                        2'b11: begin
                            // ACK生成完成
                        end
                    endcase
                end
                
                NACK_GEN: begin
                    scl_oe <= 1;
                    
                    case (scl_phase)
                        2'b00: begin
                            sda_oe <= 1;
                            sda_out <= 1; // 生成NACK
                        end
                        2'b01: begin
                            // SCL高电平，从设备检测NACK
                        end
                        2'b10: begin
                            scl_out <= 0; // SCL拉低
                        end
                        2'b11: begin
                            // NACK生成完成
                        end
                    endcase
                end
                
                STOP: begin
                    scl_oe <= 1;
                    
                    case (scl_phase)
                        2'b00: begin
                            sda_oe <= 1;
                            sda_out <= 0; // SDA拉低（准备生成停止条件）
                        end
                        2'b01: begin
                            scl_out <= 1; // SCL先拉高
                        end
                        2'b10: begin
                            sda_out <= 1; // SDA在SCL高时拉高，生成停止条件
                        end
                        2'b11: begin
                            o_busy <= 0; // 清除忙标志
                        end
                    endcase
                end
                
                ARB_LOST: begin
                    // 仲裁失败处理逻辑
                    sda_oe <= 0;
                    sda_out <= 1;
                    scl_oe <= 0;
                    o_busy <= 0;
                    arbitration_lost <= 0; // 清除仲裁丢失标志
                end
            endcase
        end
    end
    
    // 总线输出控制 - 开漏输出
    assign sda = sda_oe ? ((sda_out == 0) ? 1'b0 : 1'bz) : 1'bz;
    assign scl = scl_oe ? ((scl_out == 0) ? 1'b0 : 1'bz) : 1'bz;
    
endmodule